package com.xrh.core.generater.utils;

import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;

import com.xrh.core.bean.Queryer;
import com.xrh.core.bean.SQLQueryerParser;
import com.xrh.core.generater.bean.ColumnBean;
import com.xrh.core.generater.bean.DataBaseConfig;
import com.xrh.core.generater.bean.ProgressBean;
import com.xrh.core.generater.bean.TableInfo;
import com.xrh.core.generater.bean.UserInput;
import com.xrh.core.generater.interfaces.Console;
import com.xrh.core.utils.PagingUtils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * @function 数据库工具类(只适用于mysql)
 * @author ronghui.xiao
 * @date 2017年6月24日
 */
@Slf4j
public class DataBaseUtils {
	
	/**
	 * @function 根据数据库配置信息获取表信息map
	 * @author ronghui.xiao
	 * @throws Exception 
	 * @date 2017年6月24日
	 */
	public static Map<String , TableInfo> getTableInfoMap(Console console , DataBaseConfig dataBaseConfig) throws Exception{
		
		//1. JDBC连接MYSQL的代码很标准。 
		Connection conn = getConnection(dataBaseConfig);

		//2. 下面就是获取表的信息。 
		DatabaseMetaData  databaseMetaData = conn.getMetaData(); 
		
		//3.获取所有的表名集合
		List<String> tableNames = getTableNames(conn , dataBaseConfig , databaseMetaData);
		
		Map<String , TableInfo> tableInfoMap = new HashMap<>();
		
		
		for(String tableName : tableNames){
			
			try {
				
				TableInfo tableInfo = new TableInfo();
				
				tableInfo.setTableName(tableName);
				tableInfo.setClassName(RegexUtils.generateClassName(tableName));
				tableInfo.setLittleClassName(MyStringUtils.lowercaseIndexLetter(tableInfo.getClassName()));
				
				console.console("正在获取表"+tableName+"的备注");
				String tableComment = getCommentByTableName(conn , tableName);
				tableInfo.setComment(StringUtils.isBlank(tableComment) ? tableInfo.getClassName() : tableComment);
				
				console.console("正在获取表"+tableName+"的主键集合");
				tableInfo.setPrimaryKeys(getPrimaryKeys(conn.getCatalog() , dataBaseConfig , databaseMetaData, tableName));
				
				console.console("正在获取表"+tableName+"的字段信息");
				LinkedHashMap<String , ColumnBean> columnInfoMap = getColumnInfoMap(conn.getCatalog() , databaseMetaData , tableInfo);
				tableInfo.setColumnInfoMap(columnInfoMap);
				
				tableInfoMap.put(tableName, tableInfo);
				
			} catch (Exception e) {
				console.console("正在获取表"+tableName+"的信息发生错误==>");
				e.printStackTrace();
			}
			
		}
		
		conn.close();
		
		return tableInfoMap;
	}
	
	/**
	 * @function 获取数据库名称列表
	 * @param dataBaseConfig
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static  List<String> getDbNameList(DataBaseConfig dataBaseConfig) throws Exception{
		
		//1. JDBC连接MYSQL的代码很标准。 
		Connection conn = getConnection(dataBaseConfig);

		//2. 下面就是获取表的信息。 
		DatabaseMetaData  databaseMetaData = conn.getMetaData(); 
		
		ResultSet resultSet = databaseMetaData.getCatalogs();
		
		List<String> dbNameList = new ArrayList<String>();
		
		while (resultSet.next()) {
			dbNameList.add(StrUtil.trim(resultSet.getString("TABLE_CAT")));
		}
		
		conn.close();
		
		return dbNameList;
	}
	
	/**
	 * @function 获取创建表的DDL语句
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static String getCreateTableDDL(DataBaseConfig dataBaseConfig , String tableName) throws Exception {
		
		String sql = "show create table " + tableName;
		
		//1. JDBC连接MYSQL的代码很标准。 
		Connection conn = getConnection(dataBaseConfig);
		
		Statement statement = conn.createStatement();
	    
	    ResultSet rs = statement.executeQuery(sql);
		
	    String ddl = "";
	    
		while (rs.next()) {
			ddl = rs.getString(2);
			break;
		}
		
		conn.close();
		
		return ddl;
		
	}
	
	
	
	
	/**
	 * @function 获取数据库所有表的名称集合
	 * @author ronghui.xiao
	 * @date 2017年6月24日
	 */
	public static List<String> getTableNames(DataBaseConfig dataBaseConfig) throws Exception{
		
		List<String> list = new ArrayList<String>();
		
		//1. JDBC连接MYSQL的代码很标准。 
		Connection conn = getConnection(dataBaseConfig);

		//2. 下面就是获取表的信息。 
		DatabaseMetaData  databaseMetaData = conn.getMetaData(); 
		
		///*其中"%"就是表示*的意思，也就是任意所有的意思。其中m_TableName就是要获取的数据表的名字，如果想获取所有的表的名字，就可以使用"%"来作为参数了。*/
		ResultSet tableRet = databaseMetaData.getTables(conn.getCatalog(), dataBaseConfig.getDataBaseName()  , null ,  new String[]{"TABLE"}); 
		
		while(tableRet.next()){
			list.add(tableRet.getString("TABLE_NAME"));
		}
		
		conn.close();
		
		return list;
	}
	
	/**
     * @function 创建查询SQL
     * @author 肖荣辉
     * @date 2023年1月4日
     */
    public static String createQuerySql(Queryer queryer) {
    	
    	if(queryer == null)
    		return "";
    	
    	String querySql = queryer.parseQueryer(new SQLQueryerParser());
    	
    	querySql = StrUtil.isBlank(querySql) ? "" : " where " + querySql;
    	
    	return querySql;
    }
    
	/**
	 * @function 分页查询某个表的记录总条数
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static long findRecordListCountByQueryer(DataBaseConfig dataBaseConfig , String tableName , Queryer  queryer)  throws Exception {
		
        long count = 0;

        //获取表名
        Connection conn = getConnection(dataBaseConfig);

        Statement statement = conn.createStatement();

        String sql = "select count(*) COUNT from " + tableName + createQuerySql(queryer);

        log.info("执行统计SQl : {}" , sql);
        
        ResultSet  rs = statement.executeQuery(sql);

        while (rs != null && rs.next()) {
            count = rs.getLong("COUNT");
            break;
        }
        
        conn.close();
            
        return count;
	}
    
	/**
	 * @function 分页查询某个表的其中一条记录
	 * @author 肖荣辉
	 * @date 2023年5月30
	*/
	public static <T> T findOneRecordByQueryer(Class<T> clazz , DataBaseConfig dataBaseConfig , String tableName , Queryer  queryer)  throws Exception {
		
		List<LinkedHashMap<String , Object>> list = findRecordListByQueryer(dataBaseConfig , tableName , queryer , 1 , 1);
		
		return CollUtil.isEmpty(list) ? null : BeanUtil.copyProperties(list.get(0), clazz);
	}
	
	/**
	 * @function 分页查询某个表的记录集合
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static <T> List<T> findRecordListByQueryer(Class<T> clazz , DataBaseConfig dataBaseConfig , String tableName , Queryer  queryer , int pageNum , int pageSize)  throws Exception {
		
		List<LinkedHashMap<String , Object>> list = findRecordListByQueryer(dataBaseConfig, tableName, queryer, pageNum, pageSize);
		
		return CollUtil.isEmpty(list) ? new ArrayList<T>() : BeanUtil.copyToList(list , clazz);
	}
	
	/**
	 * @function 分页查询某个表的记录集合
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static List<LinkedHashMap<String , Object>> findRecordListByQueryer(DataBaseConfig dataBaseConfig , String tableName , Queryer  queryer , int pageNum , int pageSize)  throws Exception {
		
		String querySql = createQuerySql(queryer);
		
		//要查询的字段
		String selectColumnTxt = CollUtil.isNotEmpty(queryer.getSelectColumnNames()) ? StrUtil.join(",", queryer.getSelectColumnNames()) : "*";
		
	    String sql = "select "+selectColumnTxt+" from " + tableName +  querySql + " limit " + PagingUtils.getOffset(pageNum, pageSize) + " , " + pageSize;
	    
	    return executeQuerySQL(dataBaseConfig , sql);
	}
	
	/**
	 * @function 分页查询某个表的记录集合
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static List<LinkedHashMap<String , Object>> findRecordListByTableName(DataBaseConfig dataBaseConfig , String tableName , int pageNum , int pageSize)  throws Exception {
		
	    String sql = "select * from " + tableName +  " limit " + PagingUtils.getOffset(pageNum, pageSize) + " , " + pageSize;
		
	    return executeQuerySQL(dataBaseConfig , sql);
	}
	
	/**
	 * @function 执行查询SQL
	 * @param sql
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static <T> List<T>  executeQuerySQL(Class<T> clazz , DataBaseConfig dataBaseConfig , String sql)  throws Exception{
		
		List<LinkedHashMap<String , Object>> list = executeQuerySQL(dataBaseConfig, sql);
		
		return CollUtil.isEmpty(list) ? new ArrayList<T>() : BeanUtil.copyToList(list , clazz);
	}
	
	/**
	 * @function 执行查询SQL
	 * @param sql
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static  List<LinkedHashMap<String , Object>>  executeQuerySQL(DataBaseConfig dataBaseConfig , String sql)  throws Exception{
		
		//1. JDBC连接MYSQL的代码很标准。 
		Connection conn = getConnection(dataBaseConfig);
		
	    Statement statement = conn.createStatement();
	    
	    ResultSet rs = statement.executeQuery(sql);
	    
	    List<String> columnNameList = new ArrayList<String>();
	    
	    int size = rs.getMetaData().getColumnCount();
	    
	    ResultSetMetaData metaData = rs.getMetaData();
	    
	    Map<String , String> columnNameTypeMap = new HashMap<String, String>();
	    
	    for(int i = 1 ; i <= size ; i ++) {
	    	
	    	//获取别名，没有别名则获取真实列名
	    	String columnName = metaData.getColumnLabel(i);
	    	
	    	columnNameTypeMap.put(columnName , metaData.getColumnTypeName(i));
	    	
	    	columnNameList.add(columnName);
	    }
	    
	    List<LinkedHashMap<String , Object>> recordList = new ArrayList<LinkedHashMap<String,Object>>();
	    
        while (rs != null && rs.next()) {
        	
    	    LinkedHashMap<String , Object> record = new LinkedHashMap<String, Object>();
    	    
        	for(String columnName: columnNameList) {
        		
        		Object object = rs.getObject(columnName);
        		
        		String type = columnNameTypeMap.get(columnName);
        		
        		if("TIMESTAMP".equals(type) && object != null )
        			object = DateUtil.formatDateTime(Convert.toDate(object));
        		
        		if("DATE".equals(type) && object != null )
        			object = DateUtil.formatDate(Convert.toDate(object));
        		
        		if("DATETIME".equals(type) && object != null )
        			object = DateUtil.formatDateTime(Convert.toDate(object));
        		
        		record.put(columnName , object);
        	}
        	
        	recordList.add(record);

        }
	    
	   conn.close();
	   
	   return recordList;
	}
	
	/**
	 * @function 执行查询SQL
	 * @param sql
	 * @author 肖荣辉
	 * @date 2022年9月16日
	*/
	public static  int  executeUpdateSQL(DataBaseConfig dataBaseConfig , String sql)  throws Exception{
		
		//1. JDBC连接MYSQL的代码很标准。 
		Connection conn = getConnection(dataBaseConfig);
		
	    Statement statement = conn.createStatement();
	    
	    int count = statement.executeUpdate(sql);
	    
	   conn.close();
	   
	   return count;
	}
	
	
	/**
	 * @function 根据数据库配置信息获取表信息map
	 * @author ronghui.xiao
	 * @throws Exception 
	 * @date 2017年6月24日
	 */
	public static Map<String , TableInfo> getTableInfoMap(DataBaseConfig dataBaseConfig) throws Exception{
		
		//1. JDBC连接MYSQL的代码很标准。 
		Connection conn = getConnection(dataBaseConfig);
		
		//2. 下面就是获取表的信息。 
		DatabaseMetaData  databaseMetaData = conn.getMetaData(); 
		
		//3.获取所有的表名集合
		List<String> tableNames = getTableNames(conn , dataBaseConfig , databaseMetaData);
		
		Map<String , TableInfo> tableInfoMap = new HashMap<>();
		
		
		for(String tableName : tableNames){
			
			try {
				
				TableInfo tableInfo = new TableInfo();
				
				tableInfo.setTableName(tableName);
				
				System.out.println("正在获取表"+tableName+"的备注");
				tableInfo.setComment(getCommentByTableName(conn , tableName));
				
				System.out.println("正在获取表"+tableName+"的主键集合");
				tableInfo.setPrimaryKeys(getPrimaryKeys(conn.getCatalog() , dataBaseConfig , databaseMetaData, tableName));
				
				System.out.println("正在获取表"+tableName+"的字段信息");
				LinkedHashMap<String , ColumnBean> columnInfoMap = getColumnInfoMap(conn.getCatalog() , databaseMetaData , tableInfo);
				tableInfo.setColumnInfoMap(columnInfoMap);
				
				tableInfoMap.put(tableName, tableInfo);
				
			} catch (Exception e) {
				System.out.println("正在获取表"+tableName+"的信息发生错误==>");
				e.printStackTrace();
			}
			
		}
		
		conn.close();
		
		return tableInfoMap;
	}
	
	/**
	 * @function 根据数据库配置信息获取表信息map
	 * @author ronghui.xiao
	 * @throws Exception 
	 * @date 2017年6月24日
	 */
	public static Map<String , TableInfo> getTableInfoMap(DataBaseConfig dataBaseConfig , ProgressBean progressBean) throws Exception{
		
		//1. JDBC连接MYSQL的代码很标准。 
		Connection conn = getConnection(dataBaseConfig);

		//2. 下面就是获取表的信息。 
		DatabaseMetaData  databaseMetaData = conn.getMetaData(); 
		
		//3.获取所有的表名集合
		List<String> tableNames = getTableNames(conn , dataBaseConfig , databaseMetaData);
		
		Map<String , TableInfo> tableInfoMap = new HashMap<>();
		
		progressBean.setMaxValue(tableNames.size() == 0 ? -1 : tableNames.size());
		
		for(String tableName : tableNames){
			
			try {
				
				TableInfo tableInfo = new TableInfo();
				
				tableInfo.setTableName(tableName);
				
				System.out.println("正在获取表"+tableName+"的备注");
				tableInfo.setComment(getCommentByTableName(conn , tableName));
				
				System.out.println("正在获取表"+tableName+"的主键集合");
				tableInfo.setPrimaryKeys(getPrimaryKeys(conn.getCatalog() , dataBaseConfig , databaseMetaData, tableName));
				
				System.out.println("正在获取表"+tableName+"的字段信息");
				LinkedHashMap<String , ColumnBean> columnInfoMap = getColumnInfoMap(conn.getCatalog() , databaseMetaData , tableInfo);
				tableInfo.setColumnInfoMap(columnInfoMap);
				
				tableInfoMap.put(tableName, tableInfo);
				
			} catch (Exception e) {
				System.out.println("正在获取表"+tableName+"的信息发生错误==>");
				e.printStackTrace();
			}
			
			progressBean.setCurrentValue(progressBean.getCurrentValue() + 1);
			
		}
		
		conn.close();
		
		return tableInfoMap;
	}
	
	
	/**
	 * @function 验证是否是不包含的属性()
	 * @author ronghui.xiao
	 * @date 2017年7月28日
	 */
	private static boolean isExcludeColumnName(String columnName){
		return "stampDate".equals(columnName);
	}
	
	/**
	 * @function 根据表名获取key为字段名,value为字段信息的map
	 * @author ronghui.xiao
	 * @date 2017年6月24日
	 */
	private static LinkedHashMap<String , ColumnBean> getColumnInfoMap(String catalog ,  DatabaseMetaData databaseMetaData , TableInfo tableInfo) throws SQLException{
		
		LinkedHashMap<String,ColumnBean> resultMap = new LinkedHashMap<String, ColumnBean>();
		
		String tableName = tableInfo.getTableName();
		
		//ResultSet参考 https://www.cnblogs.com/chinafine/articles/1847205.html
		//提取表内的字段的名字和类型 
		ResultSet colRet = databaseMetaData.getColumns(catalog,"%",tableName,"%"); 
		
		while(colRet.next()) { 
			
			String columnName = colRet.getString("COLUMN_NAME").trim();
			
			//若是不必要的属性,则跳过
			if(isExcludeColumnName(columnName))
				continue;
			
			String comment = colRet.getString("REMARKS").trim();
			String type = colRet.getString("TYPE_NAME").trim();
			int length = colRet.getInt("COLUMN_SIZE");
			ColumnBean columnBean = new ColumnBean(tableInfo , columnName, length, comment, type);
			
//			System.out.println("字段名  " + columnName + " , 属性名:" + columnBean.getPropertyName());
			resultMap.put(columnName , columnBean);
		}
		
		return resultMap;
	}
	
	/**
	* @function 获取表中的主键集合
	* @author 肖荣辉
	 * @throws SQLException 
	* @date 2018年11月8日
	*/
	private static Collection<String> getPrimaryKeys(String catalog , DataBaseConfig config ,  DatabaseMetaData databaseMetaData  , String tableName) throws SQLException{
		
		 Collection<String> primaryKeys = new HashSet<>();
		 
		ResultSet primaryKeySet = databaseMetaData.getPrimaryKeys(catalog, config.getDataBaseName(), tableName);
		
		while(primaryKeySet.next()){
			primaryKeys.add(primaryKeySet.getString("COLUMN_NAME"));
		}
		
		return primaryKeys;
	}
	
	/**
	 * @function 获取数据库所有表的名称集合
	 * @author ronghui.xiao
	 * @date 2017年6月24日
	 */
	private static List<String> getTableNames(Connection conn , DataBaseConfig config , DatabaseMetaData databaseMetaData) throws SQLException{
		
		List<String> list = new ArrayList<String>();
		
		///*其中"%"就是表示*的意思，也就是任意所有的意思。其中m_TableName就是要获取的数据表的名字，如果想获取所有的表的名字，就可以使用"%"来作为参数了。*/
		ResultSet tableRet = databaseMetaData.getTables(conn.getCatalog(), config.getDataBaseName()  , null ,  new String[]{"TABLE"}); 
		
		while(tableRet.next()){
			list.add(tableRet.getString("TABLE_NAME"));
		}
		
		return list;
	}
	
	//获取表名和键值对的集合
	public static String  getCommentByTableName(UserInput userInput , String tableName) throws Exception {
       
		Connection conn = getConnection(userInput.getConfig());
		
		Statement statement = conn.createStatement();
		
	    ResultSet rs = statement.executeQuery("SHOW CREATE TABLE " + tableName);
	    
	    String comment = null;
		  
        if (rs != null && rs.next()) {
             String create = rs.getString(2);
             comment = getTableComment(create);
        }
         
       rs.close();
       conn.close();
        
       return comment;
        
    }
	
	//获取表名和键值对的集合
	public static String  getCommentByTableName(Connection conn , String tableName) throws Exception {
       
		Statement statement = conn.createStatement();
		
	    ResultSet rs = statement.executeQuery("SHOW CREATE TABLE " + tableName);
	    
	    String comment = null;
		  
        if (rs != null && rs.next()) {
             String create = rs.getString(2);
             comment = getTableComment(create);
        }
         
       return comment;
        
    }
		
	
	//获取表名和键值对的集合
	public static Map<String,String> getCommentByTableName(UserInput userInput , Collection<String> tableNameList) throws Exception {
       
		Map<String,String> map = new HashMap<String,String>();
        
		Connection conn = getConnection(userInput.getConfig());
		
		Statement statement = conn.createStatement();
        
        for(String tableName : tableNameList){
              ResultSet rs = statement.executeQuery("SHOW CREATE TABLE " + tableName);
              if (rs != null && rs.next()) {
                  String create = rs.getString(2);
                  String comment = getTableComment(create);
                  map.put(tableName ,  comment);
              }
              rs.close();
        }
        
        conn.close();
        
        return map;
    }
	 
	 //获取表注释
    public static String getTableComment(String tableMetaInfo) {
        String comment = null;
        int index = tableMetaInfo.indexOf("COMMENT='");
        if (index < 0) {
            return "";
        }
        comment = tableMetaInfo.substring(index + 9);
        comment = comment.substring(0, comment.length() - 1);
        return comment;
    }
	
	/**
	 * @function 获取链接
	 * @author ronghui.xiao
	 * @throws ClassNotFoundException 
	 * @throws SQLException 
	 * @date 2017年6月24日
	 */
	private static Connection getConnection(DataBaseConfig config) throws ClassNotFoundException, SQLException {
	    String driver = "com.mysql.cj.jdbc.Driver";
	    String url = config.getJdbcUrl();
	    String username = config.getDbUserName();
	    String password = config.getPassword();
	    Connection conn = null;
        Class.forName(driver); 
        conn = (Connection) DriverManager.getConnection(url, username, password);
	    return conn;
	}
	
}
